package com.monika.main.system.util.cursor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.sql.*;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author:whh
 * @date: 2025-05-20 22:38
 * <p></p>
 */
public class JdbcCursor<T> implements Iterable<T>,AutoCloseable{


    public static Logger logger = LoggerFactory.getLogger(JdbcCursor.class);

    private Connection conn;

    private PreparedStatementSetter pst;
    private PreparedStatement ps;
    private ResultSet rs;

    private RowMapper rowMapper;




    private String sql;

    public JdbcCursor() {
    }
    public JdbcCursor<T> connection(Connection conn){
        this.conn=conn;
        return this;
    }

    public JdbcCursor<T> sql(String sql){
        this.sql=sql;
        return this;
    }



    public JdbcCursor<T> preparedStatementSetter(PreparedStatementSetter pst){
        this.pst=pst;
        return this;
    }


    public JdbcCursor<T> rowMapper(RowMapper rowMapper){
        this.rowMapper=rowMapper;
        return this;
    }

    public JdbcCursor<T> cursor() throws Exception {
        try{

            if (conn == null) {
                throw new IllegalArgumentException("connection cannot be null");
            }
            if (sql == null) {
                throw new IllegalArgumentException("sql cannot be null");
            }
            if(rowMapper == null){
                rowMapper= new DefaultMapRowMapper();
            }
            ps=conn.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
            ps.setFetchSize(50);
            ps.setFetchDirection(ResultSet.FETCH_FORWARD);//按游标从头到尾取数
            if(pst!=null){
                pst.setValues(ps);
            }
            rs=ps.executeQuery();
        }catch (Exception ex){
            close();
            throw ex;
        }

        return this;
    }







    @Override
    public void close()  {

       closeResultSet(rs);
       rs=null;
       closeStatement(ps);
       releaseConnection(conn);
    }

    @Override
    public Iterator<T> iterator() {
        return new DefaultIterator<>();
    }



    private class DefaultIterator<T> implements Iterator<T>{
        private int rowNum=0;
        @Override
        public boolean hasNext() {
            try {
                return rs.next();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        @Override
        public T next() {
            try {
                return (T) rowMapper.mapRow(rs, rowNum++);
            } catch (SQLException e) {
                return null;
            }
        }
    }










     public void closeStatement( Statement stmt) {
        if (stmt != null) {
            try {
                stmt.close();
            }
            catch (SQLException ex) {
                logger.trace("Could not close JDBC Statement", ex);
            }
            catch (Throwable ex) {
                // We don't trust the JDBC driver: It might throw RuntimeException or Error.
                logger.trace("Unexpected exception on closing JDBC Statement", ex);
            }
        }
    }



        public  void releaseConnection(Connection con) {
        try {
            if (con != null) {
                con.close();
            }
        }
        catch (SQLException ex) {
            logger.debug("Could not close JDBC Connection", ex);
        }
        catch (Throwable ex) {
            logger.debug("Unexpected exception on closing JDBC Connection", ex);
        }
    }



     public void closeResultSet(ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            }
            catch (SQLException ex) {
                logger.trace("Could not close JDBC ResultSet", ex);
            }
            catch (Throwable ex) {
                // We don't trust the JDBC driver: It might throw RuntimeException or Error.
                logger.trace("Unexpected exception on closing JDBC ResultSet", ex);
            }
        }
    }


    /**
     * 默认结果映射器
     */
    public class DefaultMapRowMapper implements RowMapper<Map<String, Object>> {

        @Override
        public Map<String, Object> mapRow(ResultSet rs, int rowNum) throws SQLException {
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            Map<String, Object> mapOfColumnValues = createColumnMap(columnCount);
            for (int i = 1; i <= columnCount; i++) {
                String column = rsmd.getColumnLabel(i);
                if (column == null || column.isEmpty()) {
                    column = rsmd.getColumnName(i);
                }

                mapOfColumnValues.putIfAbsent(getColumnKey(column), getColumnValue(rs, i));
            }
            return mapOfColumnValues;
        }

        protected Map<String, Object> createColumnMap(int columnCount) {
            return new LinkedHashMap<>(columnCount);
        }

        protected String getColumnKey(String columnName) {
            return columnName;
        }

        protected Object getColumnValue(ResultSet rs, int index) throws SQLException {
            Object obj = rs.getObject(index);
            String className = null;
            if (obj != null) {
                className = obj.getClass().getName();
            }
            if (obj instanceof Blob) {
                Blob blob = (Blob) obj;
                obj = blob.getBytes(1, (int) blob.length());
            }
            else if (obj instanceof Clob) {
                Clob clob = (Clob) obj;
                obj = clob.getSubString(1, (int) clob.length());
            }
            else if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) {
                obj = rs.getTimestamp(index);
            }
            else if (className != null && className.startsWith("oracle.sql.DATE")) {
                String metaDataClassName = rs.getMetaData().getColumnClassName(index);
                if ("java.sql.Timestamp".equals(metaDataClassName) || "oracle.sql.TIMESTAMP".equals(metaDataClassName)) {
                    obj = rs.getTimestamp(index);
                }
                else {
                    obj = rs.getDate(index);
                }
            }
            else if (obj instanceof java.sql.Date) {
                if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) {
                    obj = rs.getTimestamp(index);
                }
            }
            return obj;
        }

    }


    public interface PreparedStatementSetter {

        /**
         * Set parameter values on the given PreparedStatement.
         * @param ps the PreparedStatement to invoke setter methods on
         * @throws SQLException if an SQLException is encountered
         * (i.e. there is no need to catch SQLException)
         */
        void setValues(PreparedStatement ps) throws SQLException;

    }


    public interface RowMapper<T> {

        /**
         * Implementations must implement this method to map each row of data
         * in the ResultSet. This method should not call {@code next()} on
         * the ResultSet; it is only supposed to map values of the current row.
         * @param rs the ResultSet to map (pre-initialized for the current row)
         * @param rowNum the number of the current row
         * @return the result object for the current row (may be {@code null})
         * @throws SQLException if an SQLException is encountered getting
         * column values (that is, there's no need to catch SQLException)
         */
        T mapRow(ResultSet rs, int rowNum) throws SQLException;

    }
}
